using System.Reflection;

namespace SeataNet.Core.Interceptors
{
    /// <summary>
    /// 方法调用接口
    /// </summary>
    public interface IMethodInvocation
    {
        /// <summary>
        /// 目标对象
        /// </summary>
        object Target { get; }

        /// <summary>
        /// 方法
        /// </summary>
        MethodInfo Method { get; }

        /// <summary>
        /// 方法参数
        /// </summary>
        object[] Arguments { get; }

        /// <summary>
        /// 方法返回值
        /// </summary>
        object ReturnValue { get; set; }

        /// <summary>
        /// 执行方法
        /// </summary>
        void Proceed();

        /// <summary>
        /// 异步执行方法
        /// </summary>
        Task<object> ProceedAsync();

        /// <summary>
        /// 目标类型
        /// </summary>
        Type TargetType { get; }
    }

    /// <summary>
    /// 方法拦截器接口
    /// </summary>
    public interface IMethodInterceptor
    {
        /// <summary>
        /// 拦截方法调用
        /// </summary>
        /// <param name="invocation">方法调用</param>
        /// <returns>返回结果</returns>
        Task<object> InterceptAsync(IMethodInvocation invocation);
    }

    /// <summary>
    /// 方法调用实现
    /// </summary>
    public class MethodInvocation : IMethodInvocation
    {
        private readonly object _target;
        private readonly MethodInfo _method;
        private readonly object[] _arguments;
        private readonly Func<object[], object> _proceed;
        private readonly Func<object[], Task<object>> _proceedAsync;
        private readonly Type _targetType;

        public MethodInvocation(
            object target, 
            MethodInfo method, 
            object[] arguments, 
            Func<object[], object> proceed, 
            Func<object[], Task<object>> proceedAsync, 
            Type targetType)
        {
            _target = target;
            _method = method;
            _arguments = arguments;
            _proceed = proceed;
            _proceedAsync = proceedAsync;
            _targetType = targetType;
        }

        public object Target => _target;
        public MethodInfo Method => _method;
        public object[] Arguments => _arguments;
        public object ReturnValue { get; set; }
        public Type TargetType => _targetType;

        public void Proceed()
        {
            ReturnValue = _proceed(_arguments);
        }

        public async Task<object> ProceedAsync()
        {
            ReturnValue = await _proceedAsync(_arguments);
            return ReturnValue;
        }
    }
}